﻿/// ****************************************************************************
/// Author:	        Robin Zhu
/// Create Date:	2012-4-7
/// Purpose: 		扩展Calendar类的方法，实现ISO8601标准的日历
/// ****************************************************************************
/// Modify By		Date			Remark
/// Robin           2012-11-12      实现GetISO8601WeekOfYear方法，增加GetNextISO8601WeekDay方法
/// Robin           2014-12-19      修复GetISO8601WeekOfYear方法的Year在跨年时出错的问题
/// ****************************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace RB.Utility
{
    public struct WeekOfYear
    {
        public short Year;
        public short Week;
    }

    /// <summary>
    /// 扩展Calendar类的方法，实现ISO8601标准的日历
    /// </summary>
    public static class CalendarExtensions
    {
        /// <summary>
        /// 返回设置为当前纪元中的指定日期和周的日期
        /// </summary>
        /// <param name="calendar">日历对象</param>
        /// <param name="year">年份</param>
        /// <param name="weekOfYear">年份内的周号</param>
        /// <param name="dayOfWeek">表示一周的第一天的枚举值</param>
        /// <param name="rule">定义日历周枚举值</param>
        /// <param name="iso8601">是否遵循ISO8601</param>
        /// <returns>一个正整数，表示一年中包括 time 参数中的日期的那个星期</returns>
        public static DateTime ToDateTime(this Calendar calendar, 
            int year, int weekOfYear, DayOfWeek dayOfWeek,
            CalendarWeekRule rule = CalendarWeekRule.FirstFourDayWeek, 
            DayOfWeek firstDayOfWeek = DayOfWeek.Monday,
            bool iso8601 = true)
        {
            DateTime result;
            if (iso8601)
            {
                DateTime firstMonday = GetISO8601FirstMonday(year);
                result = firstMonday.AddDays((weekOfYear - 1) * 7);
                if (dayOfWeek == DayOfWeek.Sunday)
                {
                    result = result.AddDays(6);
                }
                else
                {
                    result = result.AddDays(dayOfWeek - DayOfWeek.Monday);
                }
            }
            else
            {
                DateTime firstDayOfYear = new DateTime(year, 1, 1);
                DateTime baseDate = firstDayOfYear.AddDays(7);
                int baseWeek = calendar.GetWeekOfYear(baseDate, rule, firstDayOfWeek);
                result = baseDate.AddDays((weekOfYear - baseWeek) * 7);
                result = result.AddDays(dayOfWeek - baseDate.DayOfWeek);
            }
            return result;
        }

        /// <summary>
        /// 获取指定日期所在的周号（按ISO8601标准计算方式）
        /// </summary>
        /// <param name="calendar">日历类</param>
        /// <param name="date">日期</param>
        /// <returns>周号及年份（有可能年份与输入参数的自然年份不同）</returns>
        public static WeekOfYear GetISO8601WeekOfYear(this Calendar calendar, DateTime date)
        {
            int year = date.Year;
            DateTime firstMonday = GetISO8601FirstMonday(year);
            if (date < firstMonday)
            {
                year--;
                firstMonday = GetISO8601FirstMonday(year);
            }
            else
            {
                DateTime firstMondayNextYear = GetISO8601FirstMonday(year + 1);
                if (firstMondayNextYear <= date)
                {
                    year++;
                    firstMonday = firstMondayNextYear;
                }
            }
            int diff = (date - firstMonday).Days;
            int week = diff / 7 + 1;

            WeekOfYear result = new WeekOfYear() { Year = (short)year, Week = (short)week };
            return result;
        }

        /// <summary>
        /// 获取当前日期的下一个指定周的
        /// </summary>
        /// <param name="calendar"></param>
        /// <param name="weekOfYear"></param>
        /// <param name="dayOfWeek"></param>
        /// <returns></returns>
        public static DateTime GetNextISO8601WeekDay(this Calendar calendar, 
            int weekOfYear, DayOfWeek dayOfWeek)
        {
            DateTime today = DateTime.Today;
            int year = today.Year;

            DateTime result = calendar.ToDateTime(year, weekOfYear, dayOfWeek);
            if (result < today)
            {
                year++;
                result = calendar.ToDateTime(year, weekOfYear, dayOfWeek);
            }
            return result;
        }

        private static DateTime GetISO8601FirstMonday(int year)
        {
            DateTime firstDayOfYear = new DateTime(year, 1, 1);
            DayOfWeek firstWeekDay = firstDayOfYear.DayOfWeek;
            if (firstWeekDay >= DayOfWeek.Monday && firstWeekDay <= DayOfWeek.Thursday)
            {
                firstDayOfYear = firstDayOfYear.AddDays(-(firstWeekDay - DayOfWeek.Monday));
            }
            else if (firstWeekDay == DayOfWeek.Sunday)
            {
                firstDayOfYear = firstDayOfYear.AddDays(1);
            }
            else
            {
                firstDayOfYear = firstDayOfYear.AddDays(DayOfWeek.Saturday - firstWeekDay + 2);
            }
            return firstDayOfYear;
        }
    }
}
